#include "file.h"

#include <iostream>
#include <sstream>
#include <algorithm>

File::File() {
    addMetadata(&emptyBlocks);
}

File::~File() {
    if (file.is_open()) {
        writeMetadata();
        file.close();
    }
}

void File::addMetadata(Serializable* serializable){
    metadata.push_back(serializable);
}

bool File::create(std::string const &filename, u_int32_t blockSize) {
    file.open(filename, std::ios::out | std::ios::trunc);
    file.close();

    file.open(filename, std::ios::in | std::ios::out | std::ios::binary);
    this->blockSize = blockSize;
    this->eofPos = 4;
    return file.good();
}

bool File::open(std::string const &filename, u_int32_t blockSize) {
    file.open(filename, std::ios::in | std::ios::out | std::ios::binary);
    this->blockSize = blockSize;
    if (file.good()) {
        readMetadata();
        return true;
    } else
        return false;
}

bool File::read(u_int32_t pos, Serializable& block) {
    if (std::binary_search(emptyBlocks.list.begin(), emptyBlocks.list.end(), pos))
        return false;
    if (getFilePosition(pos) >= eofPos)
        return false;

    file.seekg(getFilePosition(pos), std::ios::beg);
    BinaryStream stream(file, this->blockSize);
    stream.load();
    block.read(stream);
    //std::cout << "Read " << pos << " " << block.toString() << std::endl;
    return true;
}

u_int32_t File::append(Serializable& block) {
    u_int32_t pos;
    if (emptyBlocks.list.empty()) {
        pos = (eofPos - 4) / blockSize;
        eofPos += blockSize;
    } else {
        pos = emptyBlocks.list.back();
        emptyBlocks.list.pop_back();
    }
    write(pos, block);
    return pos;
}

bool File::write(u_int32_t pos, Serializable& block) {
    if (getFilePosition(pos) > eofPos)
        return false;
    if (getFilePosition(pos) == eofPos)
        eofPos += blockSize;
    file.seekp(getFilePosition(pos), std::ios::beg);
    BinaryStream stream(file, this->blockSize);
    block.write(stream);
    stream.flush();
    bool success = file.good();
    //std::cout << "Write " << pos << " " << block.toString() << std::endl;
    return success;
}

bool File::remove(u_int32_t pos){
    if (std::binary_search(emptyBlocks.list.begin(), emptyBlocks.list.end(), pos))
        return false;
    else {
        emptyBlocks.list.push_back(pos);
        std::sort(emptyBlocks.list.begin(), emptyBlocks.list.end());
        return true;
    }
}

void File::readMetadata(){
    file.seekg(0, std::ios::beg);
    file.read((char*) &eofPos, 4);
    file.seekg(eofPos, std::ios::beg);
    BinaryStream stream(file, blockSize, true);
    stream.load();
    for (Serializable* serializable : metadata)
        serializable->read(stream);
}

void File::writeMetadata(){
    file.seekp(0, std::ios::beg);
    file.write((char*) &eofPos, 4);
    file.seekp(eofPos, std::ios::beg);
    BinaryStream stream(file, blockSize, true);
    for (Serializable* serializable : metadata)
        serializable->write(stream);
    stream.flush();
}

u_int32_t File::getFilePosition(u_int32_t pos) const {
    return 4 + pos * blockSize;
}
